package im.composer.media.sound.codec;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.net.URLConnection;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;

import javax.sound.sampled.AudioFileFormat;
import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioInputStream;
import javax.sound.sampled.UnsupportedAudioFileException;
import javax.sound.sampled.spi.AudioFileReader;

import org.tritonus.share.sampled.AudioFileTypes;
import org.tritonus.share.sampled.FloatInputStream;

import im.composer.media.audio.stream.AudioByteBuffer;
import im.composer.media.sound.codec.seek.LameSeekSupport;
import javazoom.jl.decoder.Bitstream;
import javazoom.jl.decoder.BitstreamException;
import javazoom.jl.decoder.Decoder;
import javazoom.jl.decoder.Header;
import javazoom.jl.decoder.SampleBuffer;

public class LameFileReader extends AudioFileReader implements PathAwareAudioFileReader {

	private static final ByteOrder BYTEORDER = ByteOrder.BIG_ENDIAN;

	private AudioFileFormat getAudioFileFormat(InputStream stream, long len) throws UnsupportedAudioFileException, IOException {
		Bitstream bitstream = new Bitstream(stream);
		Header h;
		try {
			h = bitstream.readFrame();
		} catch (BitstreamException e) {
			throw new UnsupportedAudioFileException(e.getLocalizedMessage());
		}
		if(h==null){
			throw new UnsupportedAudioFileException("NOT valid MP3 stream!");
		}
		int channels = h.mode() == Header.SINGLE_CHANNEL ? 1 : 2;
		AudioFormat format = new AudioFormat(h.frequency(), 16, channels, true, BYTEORDER == ByteOrder.BIG_ENDIAN);
		return new AudioFileFormat(new AudioFileTypes("LAME", "mp3"), format, (int) (h.total_ms(len) / 1000f * h.frequency()));
	}

	@Override
	public AudioFileFormat getAudioFileFormat(InputStream stream) throws UnsupportedAudioFileException, IOException {
		return getAudioFileFormat(stream, -1);
	}

	@Override
	public AudioFileFormat getAudioFileFormat(URL url) throws UnsupportedAudioFileException, IOException {
		URLConnection conn = url.openConnection();
		if (!conn.getContentType().equals("audio/mpeg")) {
			throw new UnsupportedAudioFileException();
		}
		long len = conn.getContentLengthLong();
		InputStream in = url.openStream();
		return getAudioFileFormat(in, len);
	}

	@Override
	public AudioFileFormat getAudioFileFormat(File file) throws UnsupportedAudioFileException, IOException {
//		if (!file.getName().toLowerCase().endsWith(".mp3")) {
//			throw new UnsupportedAudioFileException();
//		}
		return getAudioFileFormat(new FileInputStream(file), file.length());
	}

	private AudioInputStream getAudioInputStream(Bitstream bitstream, long len, LameSeekSupport seek_support) throws UnsupportedAudioFileException, IOException {
		AudioFormat format;
		Decoder decoder = new Decoder();
		final Header[] first_frame = new Header[1];
		try {
			first_frame[0] = bitstream.readFrame();
			if(first_frame[0]==null){
				throw new UnsupportedAudioFileException("NOT valid MP3 stream!");
			}
			int channels = first_frame[0].mode() == Header.SINGLE_CHANNEL ? 1 : 2;
			int srate = first_frame[0].frequency();
			format = new AudioFormat(srate, 16, channels, true, BYTEORDER == ByteOrder.BIG_ENDIAN);
		} catch (Exception e) {
			throw new UnsupportedAudioFileException(e.getLocalizedMessage());
		}
		long length = (long) (first_frame[0].total_ms(len) / 1000f * first_frame[0].frequency());
		AudioByteBuffer abb = new AudioByteBuffer() {
			Header h = first_frame[0];

			@Override
			public void requestNewBuffer() throws IOException {
				try {
					while (true) {
						if (h == null) {
							h = bitstream.readFrame();
						}
						if (h == null) {
							break;
						}
						SampleBuffer output = (SampleBuffer) decoder.decodeFrame(h, bitstream);
						short[] s_buf = output.getBuffer();
						bitstream.closeFrame();
						h = null;
						if (output.getBufferLength() < 1) {
							continue;
						}
						ByteBuffer bb = ByteBuffer.allocate(output.getBufferLength() * 2).order(BYTEORDER);
						bb.asShortBuffer().put(s_buf, 0, output.getBufferLength());
						byte[] b = bb.array();
						offer(b);
						break;
					}
				} catch (Exception e) {
					close();
				}
			}
		};
		if (seek_support != null) {
			seek_support.setBitStream(bitstream);
		}
		AudioInputStream ais = new AudioInputStream(abb, format, length);
		FloatInputStream fis = new FloatInputStream(ais, format, length) {

			@Override
			public boolean supportsSeek() {
				return seek_support != null && seek_support.isEnabled();
			}

			@Override
			public void seek(long sample_position) throws IOException {
				abb.clear();
				seek_support.seek(sample_position);
			}
		};
		if (seek_support != null) {
			seek_support.setOutput(fis);
		}
		return fis;
	}

	@Override
	public AudioInputStream getAudioInputStream(InputStream stream) throws UnsupportedAudioFileException, IOException {
		return getAudioInputStream(new Bitstream(stream), -1, null);
	}

	@Override
	public AudioInputStream getAudioInputStream(URL url) throws UnsupportedAudioFileException, IOException {
		URLConnection conn = url.openConnection();
//		if (!conn.getContentType().equals("audio/mpeg")) {
//			throw new UnsupportedAudioFileException();
//		}
		long len = conn.getContentLengthLong();
		InputStream in = url.openStream();
		return getAudioInputStream(new Bitstream(in), len, null);
	}

	@Override
	public AudioInputStream getAudioInputStream(File file) throws UnsupportedAudioFileException, IOException {
//		if (!file.getName().toLowerCase().endsWith(".mp3")) {
//			throw new UnsupportedAudioFileException();
//		}
		return getAudioInputStream(new Bitstream(Files.newByteChannel(Paths.get(file.toURI()))), file.length(), new LameSeekSupport(file));
	}

	@Override
	public AudioFileFormat getAudioFileFormat(Path path) throws UnsupportedAudioFileException, IOException {
//		if (!path.toUri().getPath().toLowerCase().endsWith(".mp3")) {
//			throw new UnsupportedAudioFileException();
//		}
		return getAudioFileFormat(Files.newInputStream(path), Files.size(path));
	}

	@Override
	public AudioInputStream getAudioInputStream(Path path) throws UnsupportedAudioFileException, IOException {
//		if (!path.toUri().getPath().toLowerCase().endsWith(".mp3")) {
//			throw new UnsupportedAudioFileException();
//		}
		return getAudioInputStream(new Bitstream(Files.newByteChannel(path)), Files.size(path), new LameSeekSupport(path));
	}

}
